/*-------------------------------------------------------------------------
 *
 * _int_selfuncs.c
 *	  用于 intarray 运算符选择性估计的函数
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  contrib/intarray/_int_selfuncs.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "_int.h"
#include "access/htup_details.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/selfuncs.h"
#include "utils/syscache.h"

PG_FUNCTION_INFO_V1(_int_overlap_sel);
PG_FUNCTION_INFO_V1(_int_contains_sel);
PG_FUNCTION_INFO_V1(_int_contained_sel);
PG_FUNCTION_INFO_V1(_int_overlap_joinsel);
PG_FUNCTION_INFO_V1(_int_contains_joinsel);
PG_FUNCTION_INFO_V1(_int_contained_joinsel);
PG_FUNCTION_INFO_V1(_int_matchsel);


static Selectivity fc_int_query_opr_selec(ITEM *fc_item, Datum *fc_values, float4 *fc_freqs,
									   int fc_nmncelems, float4 fc_minfreq);
static int	fc_compare_val_int4(const void *fc_a, const void *fc_b);

/*
 * 默认数组选择性估计函数的包装器。
 *
 * @>、&& 和 @< 操作符的默认数组选择性操作符对于整数数组工作良好。
 * 然而，如果我们直接使用 arraycontsel 和 arraycontjoinsel 作为
 * 我们的操作符的成本估算函数，它们将无法按预期工作，因为它们查看操作符的 OID。
 * 我们的操作符表现得与内置的 anyarray 版本完全一样，但我们必须告诉成本估算函数
 * 它们对应的内置操作符。这些包装器只是将操作符 OID 替换为相应的内置操作符的 OID，
 * 并调用内置函数。
 */

Datum _int_overlap_sel(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall4(arraycontsel,
										PG_GETARG_DATUM(0),
										ObjectIdGetDatum(OID_ARRAY_OVERLAP_OP),
										PG_GETARG_DATUM(2),
										PG_GETARG_DATUM(3)));
}

Datum _int_contains_sel(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall4(arraycontsel,
										PG_GETARG_DATUM(0),
										ObjectIdGetDatum(OID_ARRAY_CONTAINS_OP),
										PG_GETARG_DATUM(2),
										PG_GETARG_DATUM(3)));
}

Datum _int_contained_sel(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall4(arraycontsel,
										PG_GETARG_DATUM(0),
										ObjectIdGetDatum(OID_ARRAY_CONTAINED_OP),
										PG_GETARG_DATUM(2),
										PG_GETARG_DATUM(3)));
}

Datum _int_overlap_joinsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall5(arraycontjoinsel,
										PG_GETARG_DATUM(0),
										ObjectIdGetDatum(OID_ARRAY_OVERLAP_OP),
										PG_GETARG_DATUM(2),
										PG_GETARG_DATUM(3),
										PG_GETARG_DATUM(4)));
}

Datum _int_contains_joinsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall5(arraycontjoinsel,
										PG_GETARG_DATUM(0),
										ObjectIdGetDatum(OID_ARRAY_CONTAINS_OP),
										PG_GETARG_DATUM(2),
										PG_GETARG_DATUM(3),
										PG_GETARG_DATUM(4)));
}

Datum _int_contained_joinsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall5(arraycontjoinsel,
										PG_GETARG_DATUM(0),
										ObjectIdGetDatum(OID_ARRAY_CONTAINED_OP),
										PG_GETARG_DATUM(2),
										PG_GETARG_DATUM(3),
										PG_GETARG_DATUM(4)));
}


/*
 * _int_matchsel -- 用于 intarray @@ query_int 的限制选择性函数
 */
Datum _int_matchsel(PG_FUNCTION_ARGS)
{
	PlannerInfo *fc_root = (PlannerInfo *) PG_GETARG_POINTER(0);

	List	   *fc_args = (List *) PG_GETARG_POINTER(2);
	int			fc_varRelid = PG_GETARG_INT32(3);
	VariableStatData fc_vardata;
	Node	   *fc_other;
	bool		fc_varonleft;
	Selectivity fc_selec;
	QUERYTYPE  *fc_query;
	Datum	   *fc_mcelems = NULL;
	float4	   *fc_mcefreqs = NULL;
	int			fc_nmcelems = 0;
	float4		fc_minfreq = 0.0;
	float4		fc_nullfrac = 0.0;
	AttStatsSlot fc_sslot;

	/*
	 * 如果表达式不是 "variable @@ something" 或 "something @@ variable"
	 * 那么返回默认估计。
	 */
	if (!get_restriction_variable(fc_root, fc_args, fc_varRelid,
								  &fc_vardata, &fc_other, &fc_varonleft))
		PG_RETURN_FLOAT8(DEFAULT_EQ_SEL);

	/*
	 * 变量应该是 int[]。我们不支持变量是 query_int 的情况。
	 */
	if (fc_vardata.vartype != INT4ARRAYOID)
		PG_RETURN_FLOAT8(DEFAULT_EQ_SEL);

	/*
	 * 如果 something 也不是常量，就无法做任何有用的事情。
	 */
	if (!IsA(fc_other, Const))
	{
		ReleaseVariableStats(fc_vardata);
		PG_RETURN_FLOAT8(DEFAULT_EQ_SEL);
	}

	/*
	 * "@@" 操作符是严格的，因此我们可以立即处理 NULL。
	 */
	if (((Const *) fc_other)->constisnull)
	{
		ReleaseVariableStats(fc_vardata);
		PG_RETURN_FLOAT8(0.0);
	}

	/* 调用者确保常量是查询，因此现在获取它 */
	fc_query = DatumGetQueryTypeP(((Const *) fc_other)->constvalue);

	/* 空查询不匹配任何内容 */
	if (fc_query->size == 0)
	{
		ReleaseVariableStats(fc_vardata);
		return (Selectivity) 0.0;
	}

	/*
	 * 获取 intarray 列的统计信息。
	 *
	 * 我们关注的是最常见元素列表和 NULL 比例。
	 */
	if (HeapTupleIsValid(fc_vardata.statsTuple))
	{
		Form_pg_statistic fc_stats;

		fc_stats = (Form_pg_statistic) GETSTRUCT(fc_vardata.statsTuple);
		fc_nullfrac = fc_stats->stanullfrac;

		/*
		 * 对于 int4 数组，默认数组类型分析函数将
		 * 收集一个最常见元素列表，即一个 int4 数组。
		 */
		if (get_attstatsslot(&fc_sslot, fc_vardata.statsTuple,
							 STATISTIC_KIND_MCELEM, InvalidOid,
							 ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS))
		{
			Assert(fc_sslot.valuetype == INT4OID);

			/*
			 * 值的数量应该比数字多三个，因为
			 * 最后三个（对于 intarray）单元用于最小值、最大值
			 * 和 NULL 频率。如果不是则返回默认。
			 */
			if (fc_sslot.nnumbers == fc_sslot.nvalues + 3)
			{
				/* 获取最低频率。 */
				fc_minfreq = fc_sslot.numbers[fc_sslot.nnumbers - (fc_sslot.nnumbers - fc_sslot.nvalues)];

				fc_mcelems = fc_sslot.values;
				fc_mcefreqs = fc_sslot.numbers;
				fc_nmcelems = fc_sslot.nvalues;
			}
		}
	}
	else
		memset(&fc_sslot, 0, sizeof(fc_sslot));

	/* 使用统计信息处理查询中的逻辑表达式 */
	fc_selec = fc_int_query_opr_selec(GETQUERY(fc_query) + fc_query->size - 1,
								fc_mcelems, fc_mcefreqs, fc_nmcelems, fc_minfreq);

	/* MCE 统计信息仅计算非 NULL 行，因此需调整 NULL 行。 */
	fc_selec *= (1.0 - fc_nullfrac);

	free_attstatsslot(&fc_sslot);
	ReleaseVariableStats(fc_vardata);

	CLAMP_PROBABILITY(fc_selec);

	PG_RETURN_FLOAT8((float8) fc_selec);
}

/*
 * 估计单个 intquery 操作符的选择性
 */
static Selectivity fc_int_query_opr_selec(ITEM *fc_item, Datum *fc_mcelems, float4 *fc_mcefreqs,
					int fc_nmcelems, float4 fc_minfreq)
{
	Selectivity fc_selec;

	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	if (fc_item->type == VAL)
	{
		Datum	   *fc_searchres;

		if (fc_mcelems == NULL)
			return (Selectivity) DEFAULT_EQ_SEL;

		fc_searchres = (Datum *) bsearch(&fc_item->val, fc_mcelems, fc_nmcelems,
									  sizeof(Datum), fc_compare_val_int4);
		if (fc_searchres)
		{
			/*
			 * 元素在 MCELEM 中。返回精确选择性（或者至少
			 * 尽可能精确，如 ANALYZE 能找到）。
			 */
			fc_selec = fc_mcefreqs[fc_searchres - fc_mcelems];
		}
		else
		{
			/*
			 * 元素不在 MCELEM 中。返回默认，但假设
			 * 选择性不超过 minfreq / 2。
			 */
			fc_selec = Min(DEFAULT_EQ_SEL, fc_minfreq / 2);
		}
	}
	else if (fc_item->type == OPR)
	{
		/* 当前查询节点是一个操作符 */
		Selectivity fc_s1,
					fc_s2;

		fc_s1 = fc_int_query_opr_selec(fc_item - 1, fc_mcelems, fc_mcefreqs, fc_nmcelems,
								 fc_minfreq);
		switch (fc_item->val)
		{
			case (int32) '!':
				fc_selec = 1.0 - fc_s1;
				break;

			case (int32) '&':
				fc_s2 = fc_int_query_opr_selec(fc_item + fc_item->left, fc_mcelems, fc_mcefreqs,
										 fc_nmcelems, fc_minfreq);
				fc_selec = fc_s1 * fc_s2;
				break;

			case (int32) '|':
				fc_s2 = fc_int_query_opr_selec(fc_item + fc_item->left, fc_mcelems, fc_mcefreqs,
										 fc_nmcelems, fc_minfreq);
				fc_selec = fc_s1 + fc_s2 - fc_s1 * fc_s2;
				break;

			default:
				elog(ERROR, "unrecognized operator: %d", fc_item->val);
				fc_selec = 0;		/* 保持编译器安静 */
				break;
		}
	}
	else
	{
		elog(ERROR, "unrecognized int query item type: %u", fc_item->type);
		fc_selec = 0;				/* 保持编译器安静 */
	}

	/* 将中间结果限制在合理范围内，以避免舍入误差 */
	CLAMP_PROBABILITY(fc_selec);

	return fc_selec;
}

/*
 * 用于在 mcelem 数组中进行二分搜索的比较函数。
 */
static int fc_compare_val_int4(const void *fc_a, const void *fc_b)
{
	int32		fc_key = *(int32 *) fc_a;
	const Datum *fc_t = (const Datum *) fc_b;

	return fc_key - DatumGetInt32(*fc_t);
}
